一个看似简单的 Gzip 压缩问题,背后却牵扯出 K8s Ingress、多层 Nginx 代理、请求头传递等一系列链路问题。本文将完整复盘一次从现象到根因的深度排查过程,并对不同解决方案进行对比和反思。
我们发现线上环境的前端静态资源 https://eu.ampaura.tech/ems/umi.55c10ef3.js 未开启 Gzip 压缩,导致文件体积过大,影响了页面加载性能。然而,检查业务容器内的 Nginx 配置后发现,Gzip 功能明明是开启的。这说明压缩在中间链路的某个环节被“熔断”了。
首先,从外部直接请求资源,确认浏览器收到的响应头中确实**不包含** Content-Encoding: gzip。
curl -sI https://eu.ampaura.tech/ems/umi.55c10ef3.js
# ...
# 响应头中未找到 Content-Encoding
# ...进入最终提供服务的 Pod (ems-front-xxxx),直接在容器内部请求 Nginx,模拟最纯粹的环境。
kubectl exec -n ems-eu ems-front-6cf688c88-vkjlb -- \
curl -sI -H 'Accept-Encoding: gzip' http://127.0.0.1/umi.55c10ef3.js结果令人惊喜:响应头中明确包含了 Content-Encoding: gzip!这证明了**业务容器自身的 Nginx 压缩是正常的**。问题一定出在上游的代理链路上。
一个常见的优化是“静态预压缩”,即在构建阶段就生成 .gz 文件。我们检查了容器内是否存在这些文件。
kubectl exec -n ems-eu ems-front-6cf688c88-vkjlb -- find /app/dist -name '*.gz'
# (无输出)结果为空,排除了使用 gzip_static 模块的可能性。压缩是在 Nginx 运行时动态进行的。
通过检查 K8s 的 Ingress 和 Service 配置,我们发现外部流量并不是直接打到 ems-front,而是先经过了一个名为 ems-common-front 的**聚合层 Nginx**。
现在,我们在聚合层 Pod 内部,去请求下游的 ems-front 服务。
kubectl exec -n ems-eu ems-common-front-d86f65654-2s8k6 -- \
curl -sI -H 'Accept-Encoding: gzip' http://ems-front/umi.55c10ef3.js结果再次确认,下游服务 (ems-front) 返回了 Gzip 压缩内容。到此,真相水落石出:问题就出在 ems-common-front 这个聚合层 Nginx 上。
通过检查 ems-common-front 的 Nginx 配置 (存储在 ConfigMap 中),我们发现了两个相互关联的缺陷:
Accept-Encoding 头: Nginx 作为反向代理,默认不会将所有客户端请求头都转发给上游。由于聚合层没有显式转发 Accept-Encoding,导致下游的 ems-front 认为客户端不支持 Gzip,因此返回了未压缩的原始内容。gzip_types 配置不完整: 聚合层 Nginx 自身虽然开启了 gzip on;,但其 gzip_types 配置列表很老旧,只包含了 application/x-javascript,而 Umi 打包后的 JS 文件 MIME 类型是 application/javascript。因此,即使下游返回了明文,聚合层也未能对其进行二次压缩。在聚合层 Nginx 的 `location` 块中,添加 proxy_set_header Accept-Encoding $http_accept_encoding;,让下游的压缩能力得到充分利用。
在聚合层 Nginx 中,将 gzip_types 补全,至少加入 application/javascript 和 application/json。
在前端构建流程中生成 .gz/.br 文件,并在两层 Nginx 中都开启 gzip_static on;。
我们采取了**“双管齐下”**的策略,以兼顾性能和兜底能力:
ems-common-front 的 ConfigMap 中添加 proxy_set_header Accept-Encoding $http_accept_encoding;,让请求头得以透传。gzip_types,确保即使下游偶尔返回明文,聚合层也能进行兜底压缩。这是一个很好的问题。在本次排查中,Ingress 的嫌疑被后置,主要基于以下判断:
Content-Encoding,但包含了 Vary: Accept-Encoding,这暗示着链路中至少有服务考虑了编码问题。如果是 Ingress 强制解压,这些头信息可能也会被清除。确实,只在聚合层补全 gzip_types 也能让浏览器收到压缩内容。但这其实是一个“将错就错”的结果:
ems-common-front) 未向上游转发 Accept-Encoding。ems-front) 收到一个“不接受压缩”的请求,因此返回了**未压缩**的 JS 文件(约 940KB)。gzip_types,它识别出 application/javascript 是需要压缩的类型,于是**自己动手**进行了压缩,并返回给浏览器。这种方式虽然能“解决”问题,但会在内网中传输更大的未压缩文件,并增加聚合层的 CPU 负载。因此,透传 Accept-Encoding 仍然是更优的实践。